#pragma once

#include "Common.hpp"
#include "SocketTemplate.hpp"
#include "Log.hpp"
#include <sys/epoll.h>

const int gdefaultfd = -1;

using namespace SocketModule;
using namespace LogModule;

class EpollServer
{
    static const int maxrevs = 64;
public:
    EpollServer(uint16_t port)
        :_port(port)
        ,_epfd(gdefaultfd)
        ,_listen_socket(std::make_unique<TcpSocket>())
        ,_isrunning(false)
    {}

    void Init()
    {
        _listen_socket->BuildTcpSocketMethod(_port);  // 创建套接字
        // 创建一个epoll模型
        _epfd = epoll_create(256);
        if(_epfd < 0)
        {
            LOG(LogLevel::ERROR) << "epoll create failed";
            exit(EPOLL_CREATE_ERR);
        }
        LOG(LogLevel::ERROR) << "epoll create success: " << _epfd;
        // 将listensockfd设置进去epoll模型
        struct epoll_event ev;
        ev.events = EPOLLIN;
        ev.data.fd = _listen_socket->Fd();
        int n = ::epoll_ctl(_epfd, EPOLL_CTL_ADD, _listen_socket->Fd(), &ev);
        if(n < 0)
        {
            LOG(LogLevel::ERROR) << "epoll_ctl add failed!";
            exit(EPOLL_CTL_ERR);
        }
    } 

    void Loop()
    {
        int timeout = -1;  // 设置超时时间，单位：毫秒
        _isrunning = true;
        while(_isrunning)
        {
            int n = ::epoll_wait(_epfd, _revs, maxrevs, timeout);
            switch(n)
            {
                case 0:
                    // 超时机制：经过timeout时间后如果没有就绪的，就返回 0
                    cout << "time out..." << endl;
                    break;
                case -1:
                    // epoll 失败
                    perror("epoll failed");
                    break;
                default:
                    // 返回值大于0表明：有 n 个fd就绪了！
                    // 在epoll中，返回给我的revs会依次存放有效的就绪事件，所以不同轮询，直接根据返回的n的个数遍历到n即可
                    std::cout << "有事件就绪了..." << std::endl;
                    // 把已经就绪的sockfd，派发给指定的模块
                    Dispatcher(n);
                    break;
            }
        }
        _isrunning = false;
    }

    // 如果是一个listensockfd，那就执行链接的操作
    void Accepter()
    {
        InetAddr client;
        // IO = 等 + 拷贝
        // 说明listensockfd已经就绪了，此时直接进行accept，不会被阻塞，相当于只会进行IO中的“拷贝”工作！
        int newfd = _listen_socket->Accepter(&client);
        if(newfd < 0) return;
        else
        {
            std::cout << "获得了一个新链接：" << newfd << " client info: " << client.Addr() << std::endl; 
            // 新获取的newfd，能立马recv吗？不能！因为不知道就没就绪，如果没就绪，就会阻塞住！所以只能先设置进_epfd的红黑树中，下一次再读取
            struct epoll_event ev;
            ev.events = EPOLLIN;
            ev.data.fd = newfd;
            int n = ::epoll_ctl(_epfd, EPOLL_CTL_ADD, newfd, &ev);
            if(n < 0)
            {
                LOG(LogLevel::ERROR) << "epoll_ctl add failed!";
                exit(EPOLL_CTL_ERR);
            }
            LOG(LogLevel::DEBUG) << "epoll_ctl add seccess!";
        }
    }

    // 如果这是一个普通的fd，那就读取
    void Recver(int fd)
    {
        char buffer[1024];
        // 此时的recv不会被阻塞，因为fd已经就绪了 
        // 这里的recv，是不完善的，因为不一定读到了所有的请求，想读完整的报文，必须得有协议，所以这里还是有bug的
        ssize_t n = ::recv(fd, buffer, sizeof(buffer)-1, 0);
        if(n > 0)
        {
            buffer[n] = 0;
            std::cout << "client# " << buffer << std::endl;
            // 回显回去
            std::string message = "echo# ";
            message += buffer;
            ::send(fd, message.c_str(), message.size(), 0);
        }
        else if(n == 0)  // 说明链接关了
        {
            LOG(LogLevel::DEBUG) << "客户端退出了，sockfd: " << fd;
            // 将fd从_epfd的红黑树中移除，不再关心它
            int m = ::epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);
            if(m < 0)
            {
                LOG(LogLevel::ERROR) << "epoll_ctl del failed!";
                return;
            }
            LOG(LogLevel::DEBUG) << "epoll_ctl del seccess!";
            // 这里关闭文件描述符时，必须保证在删除节点之后，因为上面的删除操作需要用到fd，不同于select、poll
            close(fd);
        }
        else
        {
            LOG(LogLevel::DEBUG) << "客户端读取出错了，sockfd: " << fd;
            // 将fd从_epfd的红黑树中移除，不再关心它
            int m = ::epoll_ctl(_epfd, EPOLL_CTL_DEL, fd, nullptr);
            if(m < 0)
            {
                LOG(LogLevel::ERROR) << "epoll_ctl del failed!";
                return;
            }
            LOG(LogLevel::DEBUG) << "epoll_ctl del seccess!";
            // 这里关闭文件描述符时，必须保证在删除节点之后，因为上面的删除操作需要用到fd，不同于select、poll
            close(fd);
        }
    }

    void Dispatcher(int rnum)  // 如果有越来越多的fd进来了，就可能有更多的fd就绪，就不仅仅是listensockfd
    {
        for(int i = 0;i < rnum;i++)
        {
            int event = _revs[i].events;
            int fd = _revs[i].data.fd;
            if(fd == _listen_socket->Fd())
            {
                // 这是listensockfd，获取新链接
                if(event & EPOLLIN)
                {
                    Accepter();
                }
            }
            else
            {
                // 这是普通fd
                if(event & EPOLLIN)
                {
                    // 处理读事件
                    Recver(fd);
                }
                // else if(event & EPOLLOUT)
                // {
                //     // 处理写事件
                //     // write...
                // }
            }
        }
    }

    std::string Events2Str(short events)
    {
        string s = ((events & EPOLLIN) ? "POLLIN" : "");
        s += ((events & EPOLLOUT) ? "POLLOUT" : "");
        return s;
    }

    ~EpollServer()
    {
        _listen_socket->Close();
        if(_epfd > 0)
            close(_epfd);
    }

private:
    uint16_t _port;  // 端口号
    std::unique_ptr<Socket> _listen_socket;  // tcp套接字
    bool _isrunning;  // 程序是否运行
    int _epfd;  // epoll模型的fd
    struct epoll_event _revs[maxrevs];  // 存储就绪事件
};